/*
 * This program assembles a small segment of MIPS commands written in
 * Assembly and outputs the machine code for each line.
 *
 *  Created on: Apr 21, 2014
 *  	Author: Jonathan Stone
 *      Author: Mitchell Alpert
 *
 */

#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include "util.h"

#define MAX_TOKEN_LENGTH 80 //Maximum length of any token (register name, label, imm value)
#define MAX_LABELS 100 //Maximum number of labels in the program

char buffer[MAX_TOKEN_LENGTH + 1];

//Array of instructions recognized by this program
char instructions[] = "add addi and andi beq bne j jal jr lui lw or ori sll slt slti sra srl sw syscall";

int labelsFound = 0;

struct Label { //holds a label name and its memory address
	char* name;
	int address;
} labels[MAX_LABELS];

void processLabels(const char* filename);
void processInstructions(const char* filename);
int getRegister(const char* reg);
int packRType(int rs, int rt, int rd, int shamt, int funct);
int packIType(int opcode, int rs, int rt, int imm16);
int packJType(int opcode, int imm26);

int main(int argc, char* argv[]) {
	/* Disable buffering of the standard output stream, removing the console
	   output delay in Eclipse that occurs on some systems. */
	setvbuf(stdout, NULL, _IONBF, 0);

	processLabels(argv[1]);
	processInstructions(argv[1]);

	return EXIT_SUCCESS;
}

//Reads through the file and records the name and line number of every label
void processLabels(const char* filename) {
	int currentLine = 0x400000; //MIPS starting line number
	int labeledLine = 0; //flag to indicate that a label was found on this line so
						 //doesn't get counted twice
	struct Label temp;

	FILE * in = fopen(filename, "r");

	while (getNextToken(in, buffer, MAX_TOKEN_LENGTH)) //until EOF
	{
		if (strchr(buffer, ':')) //if buffer is a label
		{
			temp.name = (char *) malloc(sizeof buffer);
			strncpy(temp.name, buffer, strlen(buffer) - 1); //copy buffer to temp.name
			temp.address = currentLine;
			labels[labelsFound] = temp;
			currentLine += 4; //counting memory addresses
			labeledLine++;
			labelsFound++;
		}
		else if (strstr(instructions, buffer) != NULL && labeledLine == 1)
		{
			labeledLine--; //there was a label found, don't count again and flip flag
		}
		else if (strstr(instructions, buffer) != NULL)
		{
			currentLine+=4; //there was no label found, but it is an instruction so count line
		}

	}
	fclose(in);
}

//Reads through the file and outputs the machine code for each instruction
void processInstructions(const char* filename) {
	int val1;
	int val2;
	int val3; //variables to store the operands of each instruction
	int incrementedCurrentLine = 0x400000; //keeps track of current instruction line + 1
	int address = 0; //address of a label
	int i;
	int encode; //encoded instruction

	FILE * in = fopen(filename, "r");

	while (getNextToken(in, buffer, MAX_TOKEN_LENGTH)) //until EOF
	{
		encode = 0;

		if (strstr(instructions, buffer) != NULL) { //if buffer is in instructions[]
			incrementedCurrentLine+=4; //incremented by memory address location
			instruction_info current = getInstructionInfo(buffer);
			if (current.type == RTYPE) {
				if (current.name == JR) {
					getNextToken(in, buffer, MAX_TOKEN_LENGTH);
					val1 = getRegister(buffer);
					encode = packRType(val1, 0, 0, 0, current.funct);
				} else if (current.name == SLL || current.name == SRA || current.name == SRL) {
					getNextToken(in, buffer, MAX_TOKEN_LENGTH);
					val1 = getRegister(buffer);
					getNextToken(in, buffer, MAX_TOKEN_LENGTH);
					val2 = getRegister(buffer);
					getNextToken(in, buffer, MAX_TOKEN_LENGTH);
					val3 = atoi(buffer);
					encode = packRType(0, val2, val1, val3, current.funct);
				} else if (current.name == SYSCALL) {
					encode = packRType(0, 0, 0, 0, current.funct);
				} else {
					getNextToken(in, buffer, MAX_TOKEN_LENGTH);
					val1 = getRegister(buffer);
					getNextToken(in, buffer, MAX_TOKEN_LENGTH);
					val2 = getRegister(buffer);
					getNextToken(in, buffer, MAX_TOKEN_LENGTH);
					val3 = getRegister(buffer);
					encode = packRType(val2, val3, val1, 0, current.funct);
				}
			} else if (current.type == JTYPE) {
				getNextToken(in, buffer, MAX_TOKEN_LENGTH);
				for (i = 0; i < labelsFound; i++) {
					if (strcmp(buffer, labels[i].name) == 0) {
						address = labels[i].address;
					}
				}
				encode = packJType(current.opcode, address / 4);
			} else if (current.type == ITYPE) {
				if (current.name == LUI) {
					getNextToken(in, buffer, MAX_TOKEN_LENGTH);
					val1 = getRegister(buffer);
					getNextToken(in, buffer, MAX_TOKEN_LENGTH);
					if (isdigit(*buffer)) { //checks first char in buffer, if isdigit then it is imm value otherwise, its a label
						val2 = atoi(buffer);
					} else {
						for (i = 0; i < labelsFound; i++) {
							if (strcmp(buffer, labels[i].name) == 0) {
								address = labels[i].address;
							}
						}
						val2 = address - incrementedCurrentLine + 1; //colculates increment pc + offset
					}
					encode = packIType(current.opcode, 0, val1, val2);
				} else if (current.name == LW || current.name == SW) {
					getNextToken(in, buffer, MAX_TOKEN_LENGTH);
					val1 = getRegister(buffer);
					getNextToken(in, buffer, MAX_TOKEN_LENGTH);
					val2 = atoi(buffer);
					getNextToken(in, buffer, MAX_TOKEN_LENGTH);
					val3 = getRegister(buffer);
					encode = packIType(current.opcode, val3, val1, val2);
				} else if (current.name == BEQ || current.name == BNE) {
					getNextToken(in, buffer, MAX_TOKEN_LENGTH);
					val1 = getRegister(buffer);
					getNextToken(in, buffer, MAX_TOKEN_LENGTH);
					val2 = getRegister(buffer);
					getNextToken(in, buffer, MAX_TOKEN_LENGTH);
					if (isdigit(*buffer) || '-' == *buffer) { //buffer is an imm value if the first char is a digit or a '-'
						val3 = atoi(buffer);
					} else {
						for (i = 0; i < labelsFound; i++) {
							if (strcmp(buffer, labels[i].name) == 0) {
								address = labels[i].address;
							}
						}
						val3 = ((address - incrementedCurrentLine) >> 2); //colculates increment pc + offset
					}
					encode = packIType(current.opcode, val1, val2, val3);
				} else {
					getNextToken(in, buffer, MAX_TOKEN_LENGTH);
					val1 = getRegister(buffer);
					getNextToken(in, buffer, MAX_TOKEN_LENGTH);
					val2 = getRegister(buffer);
					getNextToken(in, buffer, MAX_TOKEN_LENGTH);
					if (isdigit(*buffer) || '-' == *buffer) {
						val3 = atoi(buffer);
					} else {
						for (i = 0; i < labelsFound; i++) {
							if (strcmp(buffer, labels[i].name) == 0) {
								address = labels[i].address;
							}
						}
						val3 = address - incrementedCurrentLine + 1; //colculates increment pc + offset
					}
					encode = packIType(current.opcode, val2, val1, val3);
				}
			}
		}
		if (encode != 0) {
			printf("%.8x\n", encode);
		}
	}

}

//Returns the MIPS register number given the symbol. Returns -1 as an error code
int getRegister(const char* reg) {
	int ret = 0;
	int tempReg= 0;
	reg++; //skip the $
	char secondChar = *reg; //second character of register symbol
	reg++;
	char thirdChar = *reg; //third character of register symbol

	if (secondChar == 'v')
	{
		ret = atoi(&thirdChar) + 2;
	}
	else if (secondChar == 's')
	{
		if (thirdChar == 'p')
		{
			ret = 29;
		}
		else
		{
			ret = atoi(&thirdChar) + 16;
		}
	}
	else if (secondChar == 't')
	{
		tempReg = atoi(&thirdChar);
		ret = (tempReg <= 7) ? tempReg + 8 : tempReg + 16;
	}
	else if (secondChar == 'a')
	{
		if  (thirdChar == 't')
		{
			ret = 1;
		}
		else
		{
			ret = atoi(&thirdChar) + 4;
		}
	}
	else if (secondChar == 'r')
	{
		ret = 31;
	}
	else if (secondChar == 'g')
	{
		ret = 28;
	}
	else if (secondChar == 'f')
	{
		ret = 30;
	}
	else if (secondChar == 'k')
	{
		ret = atoi(&thirdChar) + 26;
	}
	else if (secondChar == 'z')
	{
		ret = 0;
	}
	else //should never hit this case, -1 indicates an error
	{
		ret = -1;
	}
	return ret;
}




int packRType(int rs, int rt, int rd, int shamt, int funct) {
	//int packRType(int rs, int rt, int rd, int shamt, int funct) {
	// All three pack functions utilize the same math. The int
	// value of each part of the instruction code is multiplied
	// by 2 to the power of the minimum bit in that section of
	// the code. The sum of these values is the int representing
	// this machine instruction.

	// opcode = 0
	// rs = rs * 2^21
	// rt = rt * 2^16
	// rd = rd * 2^11
	// shamt = shamt * 2^6
	// funct = funct * 2^0 (trivial)
	// sum all products for return value
	int ret = 0;
	ret += (rs * (2 << 20));
	ret += (rt * (2 << 15));
	ret += (rd * (2 << 10));
	ret += (shamt * (2 << 5));
	ret += funct;
	return ret;
}

int packIType(int opcode, int rs, int rt, int imm16) {
	// opcode = opcode * 2^26
	// rs = rs * 2^21
	// rt = rt * 2^16
	// imm16 = imm16 * 2^0 (trivial)
	// sum all products for return value
	int ret =  (imm16<0) ? 0xffff0000 ^ imm16 : imm16;

	ret += opcode * (2 << 25);
	ret += rs * (2 << 20);
	ret += rt * (2 << 15);

	return ret;
}

int packJType(int opcode, int imm26) {
	// opcode = opcode * 2^26
	// imm26 = imm26 * 2^0 (trivial)
	// sum all products for return value
	int ret = opcode * (2 << 25);
	ret += imm26;
	return ret;
}
